package com.gooluke.spring;

import com.gooluke.spring.annotation.Component;
import com.gooluke.spring.annotation.Value;
import com.gooluke.spring.property.Environment;
import com.gooluke.spring.property.PropertyLoader;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author gooluke
 * description
 * datetime 2025-04-07 23:05
 */
public class ApplicationContext {


    public ApplicationContext(String basePackage) throws Exception {
        initContext(basePackage);
    }

    private final Map<String, Object> ioc = new HashMap<>();

    private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

    private final Map<String, Object> loadingIoc = new HashMap<>();

    private final List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();

    private Environment environment;

    public void initContext(String basePackage) throws Exception {
        // 1. 加载配置文件
        Properties properties = PropertyLoader.loadProperties("application.properties");

        // 2. 初始化环境
        environment = new Environment(properties);

        //  3. 扫描包
        scanPackage(basePackage).stream().filter(this::scanCreate).forEach(this::createBeanDefinition);

        // 4. 初始化 BeanPostProcessor
        initBeanPostProcessors();
        beanDefinitionMap.values().forEach(this::createBean);
    }

    private void initBeanPostProcessors() {
        beanDefinitionMap.values().stream()
                .filter(beanDefinition -> BeanPostProcessor.class.isAssignableFrom(beanDefinition.getBeanType()))
                .map(this::createBean)
                .map(bean -> (BeanPostProcessor) bean).forEach(beanPostProcessors::add);
    }

    private List<Class<?>> scanPackage(String basePackage) throws Exception {
        List<Class<?>> classList = new ArrayList<>();
        Path path = Paths.get(getPath(basePackage));
        Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Path absolutePath = file.toAbsolutePath();
                if (absolutePath.toString().endsWith(".class")) {
                    String replace = absolutePath.toString().replace(File.separator, ".");
                    int packageIndex = replace.indexOf(basePackage);
                    String classPath = replace.substring(packageIndex).replace(".class", "");
                    try {
                        classList.add(Class.forName(classPath));
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }
                return FileVisitResult.CONTINUE;
            }
        });
        return classList;
    }

    public String getPath(String basePackage) throws Exception {
        URL resource = this.getClass().getClassLoader().getResource(basePackage.replace(".", File.separator));
        if (resource == null) {
            throw new IllegalArgumentException("Package not found: " + basePackage);
        }
        String decodedPath = URLDecoder.decode(resource.getFile(), "UTF-8");
        // 处理路径前缀
        if (decodedPath.startsWith("/")) {
            decodedPath = decodedPath.substring(1);
        }
        // 处理 Windows 路径中的 URL 编码
        decodedPath = decodedPath.replace("%5C", File.separator).replace("%20", " ");
        return decodedPath;
    }

    private boolean scanCreate(Class<?> clazz) {
        return clazz.isAnnotationPresent(Component.class);
    }

    protected BeanDefinition createBeanDefinition(Class<?> beanType) {
        BeanDefinition beanDefinition = new BeanDefinition(beanType);
        if (beanDefinitionMap.containsKey(beanDefinition.getName())) {
            throw new RuntimeException("bean name [ " + beanDefinition.getName() + " ] is exist");
        }
        beanDefinitionMap.put(beanDefinition.getName(), beanDefinition);
        return beanDefinition;
    }

    private Object createBean(BeanDefinition beanDefinition) {
        if (ioc.containsKey(beanDefinition.getName())) {
            return ioc.get(beanDefinition.getName());
        }
        if (loadingIoc.containsKey(beanDefinition.getName())) {
            return loadingIoc.get(beanDefinition.getName());
        }
        return doCreateBean(beanDefinition);
    }

    private Object doCreateBean(BeanDefinition beanDefinition) {
        Constructor<?> constructor = beanDefinition.getConstructor();
        Object bean = null;
        try {
            bean = constructor.newInstance();
            loadingIoc.put(beanDefinition.getName(), bean);
            valueBean(bean, beanDefinition);
            autowiredBean(bean, beanDefinition);
            bean = initializeBean(bean, beanDefinition);
            loadingIoc.remove(beanDefinition.getName());
            ioc.put(beanDefinition.getName(), bean);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return bean;
    }

    private Object initializeBean(Object bean, BeanDefinition beanDefinition) throws Exception {
        for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
            bean = beanPostProcessor.beforeInitializeBean(bean, beanDefinition.getName());
        }

        Method postConstructMethod = beanDefinition.getPostConstructMethod();
        if (postConstructMethod != null) {
            postConstructMethod.invoke(bean);
        }

        for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
            bean = beanPostProcessor.afterInitializeBean(bean, beanDefinition.getName());
        }
        return bean;
    }

    private void autowiredBean(Object bean, BeanDefinition beanDefinition) throws IllegalAccessException {
        for (Field field : beanDefinition.getAutowiredFields()) {
            field.setAccessible(true);
            field.set(bean, getBean(field.getType()));
        }
    }

    private void valueBean(Object bean, BeanDefinition beanDefinition) throws IllegalAccessException {
        for (Field field : beanDefinition.getValueFields()) {
            field.setAccessible(true);
            Value valueAnnotation = field.getAnnotation(Value.class);
            String propertyValue = resolveValue(valueAnnotation, environment);

            Class<?> fieldType = field.getType();

            // 类型转换
            if (fieldType == String.class) {
                field.set(bean, propertyValue);
            } else if (fieldType == Integer.class) {
                field.set(bean, Integer.parseInt(propertyValue));
            } else if (fieldType == Boolean.class) {
                field.set(bean, Boolean.parseBoolean(propertyValue));
            }
        }
    }

    private String resolveValue(Value valueAnnotation, Environment environment) {
        String placeholder = valueAnnotation.value();

        // 格式校验
        if (placeholder == null || placeholder.length() < 4 || !placeholder.startsWith("${") || !placeholder.endsWith("}")) {
            throw new RuntimeException("Invalid value annotation format: " + placeholder);
        }

        // 去掉 ${ 和 }，并去除前后空格
        String propertyExpression = placeholder.substring(2, placeholder.length() - 1).trim();

        // 分离 key 和 defaultValue
        String[] parts = propertyExpression.split(":", 2); // 最多分成两部分
        String propertyKey = parts[0].trim();
        String defaultValue = parts.length > 1 ? parts[1].trim() : null;

        // 从环境变量中获取值
        String propertyValue = environment.getProperty(propertyKey);

        // 如果配置文件中有值，则返回配置的值
        if (propertyValue != null) {
            return propertyValue;
        }

        // 如果配置文件中没有值，但提供了默认值，则返回默认值
        if (defaultValue != null && !defaultValue.isEmpty()) {
            return defaultValue;
        }

        // 如果配置文件中没有值，也没有默认值，则抛出异常
        throw new RuntimeException("Property '" + propertyKey + "' not found and no default value provided.");
    }

    public Object getBean(String beanName) {
        if (beanName == null) {
            return null;
        }
        Object bean = ioc.get(beanName);
        if (bean != null) {
            return bean;
        }
        if (beanDefinitionMap.containsKey(beanName)) {
            return createBean(beanDefinitionMap.get(beanName));
        }
        return null;
    }

    public <T> T getBean(Class<T> beanType) {
        String beanName = beanDefinitionMap.values().stream()
                .filter(beanDefinition -> beanDefinition.getBeanType().isAssignableFrom(beanType))
                .findAny()
                .map(BeanDefinition::getName)
                .orElse(null);
        return (T) getBean(beanName);
    }

    public <T> List<T> getBeans(Class<T> beanType) {
        return beanDefinitionMap.values().stream()
                .filter(beanDefinition -> beanDefinition.getBeanType().isAssignableFrom(beanType))
                .map(BeanDefinition::getName)
                .map(this::getBean)
                .map(bean -> (T) bean)
                .collect(Collectors.toList());
    }


}
